<?php

// QueryDatabase.php
// Michael Baker
// 2010.06.21
//
// provides functionality for interaction with OWAMP query databases

if ( !defined( "IN_MDM" ) )
  exit;
  
require_once( "Database.php" );
require_once( "NetworkObject.php" );

// A few useful functions relating to Query Databases
function device_has_db( $addr )
{
  // autodetect whether a node is an MA or MP
  // 1=MA, 0=MP. Simple database connection attempt
  // Will hang if $address does not exist on the network
  
  $link = @mysql_connect( $addr, QDB_USERNAME, QDB_PASSWORD );
  $retval = ( $link ? true : false );
  @mysql_close( $link );
  return $retval;
}

function find_dead_segs( $ip_arr )
{
  // ip_arr: an array of IPs with duplicates {a, b, b, c, c, d, d, e}
  // if the return value is array(), we can assume there are no dead segments
  
  $ip_status_map = array();
  $badNodeArr = array();
  
  for ( $k = 0; $k < count($ip_arr); $k += 2 )
  {
    $ip1 = $ip_arr[ $k ];
    $ip2 = $ip_arr[ $k+1 ];
    
    $ip1_already_scanned = array_key_exists($ip1, $ip_status_map);
    $ip2_already_scanned = array_key_exists($ip2, $ip_status_map);
    $ip1_status = ($ip1_already_scanned) ? $ip_status_map[$ip1] : device_has_db( $ip1 );
    $ip2_status = ($ip2_already_scanned) ? $ip_status_map[$ip2] : device_has_db( $ip2 );
    
    if ( !$ip1_already_scanned ) { $ip_status_map[$ip1] = $ip1_status; }
    if ( !$ip2_already_scanned ) { $ip_status_map[$ip2] = $ip2_status; }
    
    $dead = ( (!$ip1_status) && (!$ip2_status) );
    
    if ( $dead )
    {
      $badNodeArr[] = array($ip1, $ip2);
      break;
    }
  }
  
  return $badNodeArr;
}

function formulate_union( $base_query, $needle, $ymd_array, $do_all = false ) 
{
  // formulate a union of queries by taking one query and replicating
  // it for each YYYYMMDD date in an array of such strings, and then
  // taking the union of them to create a multi-date data query
  
  $joiner = ($do_all) ? " UNION ALL " : " UNION ";
  $queries = array();
  
  foreach ( $ymd_array as $ymd )
  {
    $this_query = str_replace( $needle, $ymd, $base_query );
    $queries[]  = $this_query;
  }
  
  $union_query = implode( $joiner, $queries );
  return $union_query;
}

function owamp2unix( $timestamp )
{
  // convert 64-bit OWAMP/NTP timestamp to standard UNIX timestamp
  
  return ($timestamp / QDB_NTP_SCALE) - QDB_NTP_OFFSET;
}

function unix2owamp( $timestamp )
{
  // convert standard UNIX timestamp to 64-bit OWAMP/NTP timestamp
  
  return ($timestamp + QDB_NTP_OFFSET) * QDB_NTP_SCALE;
}

function get_ymd( $unix_time = false )
{
  // get the YYYYMMDD datestamp (string) corresponding to
  // a UNIX timestamp (used for prefixing OWAMP tables)
  
  return ($unix_time) ? gmdate('Ymd', $unix_time) : gmdate('Ymd');
}

function get_ymd_array( $unix_start, $unix_end )
{
  // get an array of YYYYMMDD strings, the elements of which
  // are all the dates between the start timestamp and the
  // end timestamp
  
  $retval = array();
  
  // use a 23-hour increment to "find" all the possible days
  $inc = 60*60*23;
  for ( $cur = $unix_start; $cur < $unix_end; $cur += $inc )
  {
    $ymd = get_ymd( $cur );
    if ( !in_array( $ymd, $retval ) )
      $retval[] = $ymd;
  }
  
  // ensure that the end date is in the array, as there is a
  // chance that the loop above "missed" it
  $ymd = get_ymd( $unix_end );
  if ( !in_array( $ymd, $retval ) )
    $retval[] = $ymd;
  
  return $retval;
}

function get_tclause( $unix_start, $unix_end )
{
  // Use two unix timestamps and get a timeclause
  // Assume $unix_end > $unix_start
  // We also attach the tspec_id='...' clause here
  
  $clause  = "(etimestamp < ( $unix_end + " . QDB_NTP_OFFSET;
  $clause .= " )*POW(2, 32)) AND (stimestamp > ( $unix_start + " . QDB_NTP_OFFSET;
  $clause .= " )*POW(2, 32)) AND tspec_id='" . QDB_TESTSPEC . "'";
  
  return $clause;
}

class QueryDatabase extends Database
{
  
  private $date_prefix; // YYYYMMDD string
  
  public function __construct( $host )
  {
    // construct by a node
    
    $this->info = array( $host, QDB_USERNAME, QDB_PASSWORD );
    $this->open();
    $this->change_db( QDB_DATABASE );
  }
  
  public function get_date()
  {
    // retrieve the DB's current query date 
    return $this->date_prefix;
  }
  
  public function set_date( $prefix )
  {
    // set the DB's current query date 
    $dates = $this->get_available_dates();
    
    if ( !in_array( $prefix, $dates ) )
    {
      $ip = $this->info[ 0 ]; // access Database attribute
      
      $message  = "Date $prefix is not available in database ";
      $message .= "at $ip";
      
      throw new DatabaseException( $message );
    }
    
    $this->date_prefix = $prefix;
  }

  public function get_node_owamp_id( $node_addr )
  {
    // retrieve the OWAMP ID for a certain address
    // to be used in send_id, recv_id WHERE clauses
    
    $datePrefix = $this->get_date();
    
    $this->escape( $datePrefix );
    $this->escape( $node_addr );
    
    $tbl = $datePrefix . "_" . QDB_TBL_NODES;
    
    $query  = "SELECT node_id FROM $datePrefix" . "_" . QDB_TBL_NODES;
    $query .= " WHERE addr='$node_addr'";
    
    $this->execute( $query );
    if ( !$this->numrows() )
    {
      // IP doesn't exist in nodes!
      $host = $this->info[ 0 ];
      throw new DatabaseException( "Failed to retrieve OWAMP ID: node with address $node_addr not found in table $tbl on $host" );
    }
    
    $row = $this->fetchone();
    return $row[ 0 ];
  }
  
  public function get_nodes()
  {
    // retrieve all OWAMP Nodes from the NODES table
    // each element in the array will be of the form (ID, stitle, ltitle, IP)

    $datePrefix = $this->get_date();

    $this->escape( $datePrefix );
    $tbl = $datePrefix . "_" . QDB_TBL_NODES;

    $query  = "SELECT node_id, node_name, longname, addr FROM $tbl";
    $this->execute( $query );

    return $this->fetchall();
  }

  public function get_node_title( $node_addr )
  {
    // retrieve OWAMP Node titles (short/long)
    
    $datePrefix = $this->get_date();
    
    $this->escape( $datePrefix );
    
    try {
      $owamp_id = $this->get_node_owamp_id( $node_addr );
    }
    catch (DatabaseException $e)
    {
      return array( "(unresolved name)", "(unresolved name)" );
    }
    
    $query  = "SELECT node_name, longname FROM $datePrefix" . "_" . QDB_TBL_NODES;
    $query .= " WHERE node_id='$owamp_id'";

    $this->execute( $query );
    
    $row = $this->fetchone();
    return $row;
  }
    
  public function get_available_dates()
  {
    // retrieve a representation of the available
    // dates in the database
    
    $query  = "SELECT `year`, `month`, `day` FROM " . QDB_TBL_DATES;
    
    $this->execute( $query );
    $resultset = $this->fetchall();
    
    $dates = array();
    foreach ( $resultset as $row )
    {
      $year = str_pad( $row[ 0 ], 4, "0", STR_PAD_LEFT );
      $month = str_pad( $row[ 1 ], 2, "0", STR_PAD_LEFT );
      $day = str_pad( $row[ 2 ], 2, "0", STR_PAD_LEFT );
      
      array_push( $dates, $year . $month . $day );
    }
    
    return $dates;
  }

  public function check_date( $ymd )
  {
    // determine whether the YYYYMMDD string is currently in the database
    // this will allow us to detect when people are trying to do OWAMP
    // queries on dates that are in the future, or too far in the past
    
    $available = $this->get_available_dates();
    return ( in_array( $ymd, $available ) );
  }
  
  public function get_couple_clause( $owamp1, $owamp2 )
  {
    // use a nodecouple to obtain a unidirectional db clause

    $constraint  = "(send_id='$owamp1' AND ";    
    $constraint .= "recv_id='$owamp2')";
    
    return $constraint;
  }
  
  public function get_couple_clause2( $owamp1, $owamp2 )
  {
    // use a nodecouple to obtain a database clause

    $part1 = $this->get_couple_clause( $owamp1, $owamp2 );
    $part2 = $this->get_couple_clause( $owamp2, $owamp1 );
    
    return "($part1 AND $part2)";
  }
  
  public function has_data( $node_addr )
  {
    // look in the NODES table to see if a node is indexed
    
    try
    {
      $this->get_node_owamp_id( $node_addr );
    }
    catch (DatabaseException $e)
    {
      return 0;
    }
    
    return 1;
  }
  
}

?>
